package eg.backend.manager.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.beans.factory.annotation.Autowired;

import eg.backend.base.PermissionType;
import eg.backend.domain.Dimension;
import eg.backend.domain.DimensionPermission;
import eg.backend.domain.User;
import eg.backend.manager.DimensionManager;
import eg.backend.manager.UserManager;
import eg.backend.repository.DimensionRepository;
import eg.backend.repository.UserRepository;
import eg.backend.util.Constants;

public class DimensionManagerImpl implements DimensionManager {
	@Autowired
	private DimensionRepository dimensionRepository;
	@Autowired
	private UserRepository userRepository;
	@Autowired
	private UserManager userManager;
	@PersistenceContext
	protected EntityManager entityManager;
	private static final Logger logger = Logger.getLogger(DimensionManagerImpl.class.getName());

	//get user defined root dimensions
	public List<Dimension> getUserDimensions() {
		String currentUserId = userManager.getCurrentUserId();
		List<Dimension> rootDimensions = dimensionRepository.findUserRootDimensions(currentUserId);
		for(Dimension root : rootDimensions) {
			setDescendants(root);
		}
		return rootDimensions;
	}

	//get system root dimensions, with children dimension items based on user
	public List<Dimension> getSystemDimensions() {
		String user = userManager.getCurrentUserId();
		List<Dimension> systemProvided = dimensionRepository.findSystemRootDimensions();
		for(Dimension root : systemProvided) {
			setSystemDimensionDescendants(root, user);
		}
		return systemProvided;
	}
	
	public List<Dimension> getSystemSharedDimensions() {
		if(true)
			return new ArrayList<Dimension>();
		User user = userManager.getCurrentUser();
		Set<DimensionPermission> dimensionPermissions = user.getDimensionPermissions();
		if(dimensionPermissions == null)
			logger.warning("dimensionPermissions is null");
		else
			logger.warning("dimensionPermissions is NOT null, size = " + dimensionPermissions.size());
		List<Dimension> dimensions = new ArrayList<Dimension>();
		if(dimensionPermissions != null)
			for(DimensionPermission dp : dimensionPermissions) {
				dimensions.add(dp.getDimension());
			}
		return dimensions;
	}
	
	public List<Dimension> getOthersDimensions() {
		List<Dimension> result = new ArrayList<Dimension>();
		//TODO load his interested-in users only
		List<User> users = userRepository.loadAll();
		for(User owner : users) {
			result.addAll(this.getAvailableDimensions(owner.getId()));
		}
		return result;
	}

	public List<Dimension> getAvailableDimensions(String owner) {
		String user = userManager.getCurrentUserId();
		List<Dimension> result = new ArrayList<Dimension>();
		List<PermissionType> permissions = calculatePermission(owner, user);
//		if(permissions.size() > 0) {
//			result = dimensionRepository.findSharedDimensions(permissions, owner);
//			for(Dimension sharedRoot : result)
//				setOthersSharedDescendants(permissions, sharedRoot, owner);
//		}
		return result;
	}

	public List<Dimension> getSelfSharedDimensions() {
		String user = userManager.getCurrentUserId();
		return this.getAvailableDimensions(user);
	}
	
	/* updater */	
	//create a root dimension
	public Dimension addDimension(Dimension dimension) {
		String userId = userManager.getCurrentUserId();
		dimension.setOwner(userId);
		String userName = userManager.getCurrentUsername();//owner.getEmail();
		dimension.setOwnerName(userName);
		//没有分类这个特殊维度了, 删除这些
		if(userName.equals(Constants.USER_SYSTEM_NAME)) {
			List<String> categories = new ArrayList<String>();
			categories.add("分类");categories.add("类别");categories.add("Category");
			if(categories.contains(dimension.getName()))
				dimension.setType(Constants.TYPE_CATEGORY);
		}
		if(dimension.getType() == null)
			dimension.setType(Constants.TYPE_COMMON);
		
		if(dimension.getPermission() == null) dimension.setPermission(PermissionType.PRIVATE);
		dimension.setRootDimension(null);
		dimension.setChildren(new ArrayList<Dimension>());
		dimension = dimensionRepository.save(dimension);
		dimension.setRootDimension(dimension.getEncodedKey());//encodedKey is null here
		dimension = dimensionRepository.save(dimension);
		return dimension;
	}

	//delete a root dimension - delete the entire tree
	public void deleteDimension(Dimension dimension) {
		if(dimension == null || dimension.getParent() != null || isSystemDimension(dimension)) {
			//throw exception
			return;
		}
		dimensionRepository.delete(dimension);
	}
	public void deleteDimension(String dimensionId) {
		dimensionRepository.delete(dimensionId);
	}

	//only set permission, no need to differentiate between system and user's own.
	//so keep it separate from updateDimension(Dimension rootDimension)
	public void updateDimensionShare(List<Dimension> dimensions) {
		for(Dimension dimension : dimensions) {
			dimensionRepository.save(dimension);
		}
	}
	
	public List<Dimension> getDeleted(Dimension dim) {
		return getDeleted(dimensionRepository.get(dim.getEncodedKey()), dim);
	}
	private List<Dimension> getDeleted(Dimension old, Dimension dim) {
		List<Dimension> deleted = new ArrayList<Dimension>();
		if(dim.getEncodedKey() == null || old == null)
			return deleted;
		List<Dimension> existing = old.getChildren();
		List<Dimension> newChildren = dim.getChildren();
		for(Dimension item : existing) {
			if(item.getEncodedKey() == null)
				continue;
			Dimension dimInNew = findInList(item.getEncodedKey(), newChildren);
			if(dimInNew == null)
				deleted.add(item);
			else
				deleted.addAll(getDeleted(item, dimInNew));
		}
		return deleted;
	}
	
	private Dimension findInList(String key, List<Dimension> list) {
		for(Dimension dim : list) {
			if(dim.getEncodedKey() != null && dim.getEncodedKey().equals(key))
				return dim;
		}
		return null;
	}
	
	private boolean isDimensionInList(Dimension dimension, List<Dimension> list) {
		for(Dimension dim : list) {
			if(dim.getEncodedKey() != null && dim.getEncodedKey().equals(dimension.getEncodedKey()))
				return true;
		}
		return false;
	}
	
	public Dimension update(Dimension rootDimension) {
		Dimension root = null;
		try {
			root = dimensionRepository.get(rootDimension.getEncodedKey());
		} catch (Exception e) {
			logger.warning("NPE in get, try query for it");
			try {
				root = dimensionRepository.get(rootDimension.getEncodedKey());
			} catch (Exception e2) {
				logger.warning("Exception in getByKey" + e2.getMessage() + ", try search for it");
				List<Dimension> allDim = dimensionRepository.loadAll();
				for(Dimension dim : allDim) {
					if(dim.getEncodedKey().equals(rootDimension.getEncodedKey())) {
						root = dim; break;
					}
				}
			}
		}
//		List<Dimension> deleted = getDeleted(root, rootDimension);
		
		//1. update common properties
		root = mergeDimensionObject(root, rootDimension);		
		//2. update children
		merge(root.getChildren(), rootDimension.getChildren());
		
		//3. expenses -> moved to DimensionService, not called here
//		expenseManager.onDimensionItemsDeleted(deleted);
		
		root = dimensionRepository.save(root);
		return root;
	}

	private void updateOwner(Dimension dimension) {
		if(dimension.getOwner() == null) {
			dimension.setOwner(userManager.getCurrentUserId());
			dimension.setOwnerName(userManager.getCurrentUsername());
		}
		for(Dimension child : dimension.getChildren()) {
			updateOwner(child);
		}
	}
	
	private List<Dimension> merge(List<Dimension> old, List<Dimension> n) {
		List<Dimension> toAdd = new ArrayList<Dimension>();
		List<Dimension> toDelete = new ArrayList<Dimension>();
		for(Dimension d : old) {
			if(d.getEncodedKey() != null && d.getEncodedKey().indexOf('-') == 0)
				d.setEncodedKey(null);
			boolean exists = false;
			for(Dimension nd : n) {
				if(d.getEncodedKey().equals(nd.getEncodedKey())) {
					exists = true;
					d = mergeDimensionObject(d, nd);
					merge(d.getChildren(), nd.getChildren());
					break;
				}
			}
			if(!exists) {
				toDelete.add(d);
			} 
		}
		for(Dimension d : n) {
			updateOwner(d);
			if(d.getEncodedKey() != null) {
				try {
					long id = Long.parseLong(d.getEncodedKey());
					if(id < 0)
						d.setEncodedKey(null);
				} catch (Throwable e) {}
			}
			if(d.getEncodedKey() == null || !isDimensionInList(d, old)) {
				if(d.getOwner() == null) {
					d.setOwner(userManager.getCurrentUserId());
					d.setOwnerName(userManager.getCurrentUsername());
				}
				toAdd.add(d);
			}
		}
		old.addAll(toAdd);
		old.removeAll(toDelete);
		return old;
	}
	
//	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public Dimension update2(Dimension rootDimension) {
		Dimension root = dimensionRepository.get(rootDimension.getEncodedKey());
		root.setPermission(rootDimension.getPermission());
		root.setDescription(rootDimension.getDescription());
		root.setName(rootDimension.getName());
		root.setPosition(rootDimension.getPosition());
		root.setChildPosition(rootDimension.getChildPosition());

		List<Dimension> children = rootDimension.getChildren();
		if(children != null){
			for(Dimension child : children) {
				if(child.getOwner() == null) {
					child.setOwner(root.getOwner());
					child.setOwnerName(root.getOwnerName());
				}
			}
		}
		
		List<Dimension> todelete = new ArrayList<Dimension>();
		updateWithChildren(root, rootDimension.getChildren(), todelete);
//		expenseManager.onDimensionItemsDeleted(todelete);
		for(Dimension dimension : todelete) {
			if(!isSystemDimension(dimension))
				dimensionRepository.delete(dimension);
		}
		
		setSystemDimensionDescendants(root, userManager.getCurrentUserId());
		root = dimensionRepository.save(root);
		return root;
	}
	
	public void updateWithChildren(Dimension parent, List<Dimension> children, List<Dimension> todelete) {
		if (children == null || children.size() == 0)
			return;
		
		String currentUserId = userManager.getCurrentUserId();
		if(parent.getEncodedKey() != null)
			todelete.addAll(dimensionRepository.findAvailByParent(parent.getEncodedKey(), currentUserId, userManager.getCurrentUsername()));
		
		for (Dimension child : children) {
			List<Dimension> nextChildren = child.getChildren();
			child.setParent(parent.getEncodedKey());
			if("-1".equals(child.getEncodedKey()))
				child.setEncodedKey(null);
			if (child.getEncodedKey() == null) {
				child.setRootDimension(parent.getRootDimension() == null ? parent.getEncodedKey() : parent.getRootDimension());
				child.setOwner(currentUserId);
			} else {
				Dimension old = dimensionRepository.get(child.getEncodedKey());
				todelete.remove(old);
				child = mergeDimensionObject(old, child);
			}

			updateWithChildren(child, nextChildren, todelete);
		}
	}
	
	private Dimension mergeDimensionObject(Dimension oldDim, Dimension newDim) {
		if(!oldDim.getPosition().equals(newDim.getPosition()))
			oldDim.setPosition(newDim.getPosition());
		if(oldDim.getChildPosition() != newDim.getChildPosition())
			oldDim.setChildPosition(newDim.getChildPosition());
		if(newDim.getPermission() != null && newDim.getPermission() != oldDim.getPermission())
			oldDim.setPermission(newDim.getPermission());
		if(!isSystemDimension(oldDim) || Constants.USER_SYSTEM_NAME.equals(userManager.getCurrentUsername())) {
			if(newDim.getDescription() != null && !newDim.getDescription().equals(oldDim.getDescription()))
				oldDim.setDescription(newDim.getDescription());
			if(!oldDim.getName().equals(newDim.getName()))
				oldDim.setName(newDim.getName());
		}
//		oldDim.setChildren(newDim.getChildren());
		return oldDim;
	}
	
	private boolean isSystemDimension(Dimension dim) {
		return Constants.USER_SYSTEM_NAME.equals(dim.getOwnerName());
	}

	@SuppressWarnings("unused")
	private boolean isSystemSharedDimension(Dimension dim) {
		return Constants.USER_SYSTEMSHARED_NAME.equals(dim.getOwnerName());
	}
	
	//user's own dimension items
	private void setDescendants(Dimension parent) {
		List<Dimension> children = dimensionRepository.findAvailByParent(parent.getEncodedKey(), userManager.getCurrentUserId(), userManager.getCurrentUsername());
		if(children == null)
			return;
		for(Dimension child : children) {
			setDescendants(child);
		}
		parent.setTempChildren(children);
	}
	
	//system-provided dimension items
	private void setSystemDimensionDescendants(Dimension parent, String user) {
		List<Dimension> children = dimensionRepository.findAvailByParent(parent.getEncodedKey(), user, userManager.getCurrentUsername());
		if(children == null || children.size() == 0)
			return;
		for(Dimension child : children) {
			setSystemDimensionDescendants(child, user);
		}
		parent.setTempChildren(children);
	}
	
	private void setOthersSharedDescendants(List<PermissionType> permissions, Dimension root, User user) {
		List<Dimension> children = dimensionRepository.findSharedDimensionsByParent(root, permissions, user);
		if(children == null)
			return;
		for(Dimension child : children) {
			setOthersSharedDescendants(permissions, child, user);
		}
		root.setTempChildren(children);
	}
	
	private List<PermissionType> calculatePermission(String owner, String customer) {
		List<PermissionType> result = new ArrayList<PermissionType>();
		if(owner.equals(customer)) {
			//trick, to see self shared dimensions - remove PRIVATE
			for(PermissionType type : PermissionType.values()) {
				if(type != PermissionType.PRIVATE)
					result.add(type);
			}
		}
		else {
			result.add(PermissionType.PUBLIC);//this user can see my to-all dimensions
			result.add(PermissionType.GROUP);//this user belongs to the same group as me
		}
		return result;
	}

	public Dimension save(Dimension dimension) {
		return dimensionRepository.save(dimension);
	}

}
